跳到主要内容

buf 工具的使用

buf 是什么?

buf 是一个 Protobuf 的构建工具,它主要包含以下功能

  • 为 protobuf 提供依赖管理
  • 使用 yaml 配置简化代码生成命令
  • 提供 lint 静态检查工具
  • 提供 breaking change 静态检查工具
  • 提供 format 格式化工具
  • 自己实现 compiler 取代 protoc

安装环境

# 安装 buf 具体别的平台可以参考
# https://buf.build/docs/installation/
go install github.com/bufbuild/buf/cmd/buf@v1.21.0

依赖管理

我们知道 模块依赖 是编程语言中很常见的代码共享机制,很多语言都有自己的包管理工具,例如 NodeJS 的 npm. 众所周知 pb 也支持文件引用,但是使用方式确是十分原始 – 复制粘贴代码/文件. 这种方式非常容易造成不同步.

buf 为 pb 提供了包管理功能和包仓库(https://buf.build). 官方维护了一些常用的三方包,例如: envoyproxy/protoc-gen-validate.

可以在项目 buf.yaml 中定义依赖:

# buf.yaml
version: v1
deps:
- buf.build/envoyproxy/protoc-gen-validate

使用 buf mod update 拉取依赖,并且会生成 buf.lock 文件锁定版本

优化代码生成工作流

其实代码生成还有两个细节,一个是假如有引用需要 -I 指定所有 import path,二是源文件需要维护多语言 option.

这里举一个例子(引用最常见的 Well-Known Types):

syntax = "proto3";

package pb;

option go_package = "github.com/zcong1993/grpc-example/pb;pb";
option java_package = "com.zcong1993.example.pb";
option java_multiple_files = true;

import "google/protobuf/timestamp.proto";

message EchoRequest {
string message = 1;
google.protobuf.Timestamp ship_date = 2;
}

这时候你会发现需要一个这样的构建命令:

protoc -I. -I/usr/local/include \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
pb/origin-hello.proto

会发现需要一个 -I/usr/local/include 参数,这个参数是告诉 protoc pb import 源文件搜索范围,而且这个 include 需要手动将 protobuf release 中的 include 文件夹 copy 到本地,因为我们使用的是 protobuf 官方的扩展类型.

回过头查看源文件,有一些 option 参数,这是文件级别的面向不同语言插件的配置. go_package 和 java_package 控制的是该文件对应的生成代码的包名,原因是 pb 是支持引用的,所以生成出的代码需要引用对应的 pb 的生成代码,所以需要知道包的引用路径. 这是一件重复性很强的工作,更致命的是: 不同语言的团队可能需要修改对方的文件,增加自己语言的参数,因为不同语言可能互相不熟悉,不知道对方需要什么样的形式.

那么看看 buf 是怎么解决这两个问题的.

对于 import path,首先 buf 将 Well-Known Types 类型的源文件内置了,不需要额外指定和下载官方的源文件; 二是因为有了包管理机制,buf 会将项目 deps 指定的依赖从远端拉取缓存到本地,然后将这个目录自动包含.

对于源文件 option 参数,buf 提供了 Managed mode,其实就是支持全局配置规则.

# buf.gen.yaml
version: v1
managed:
enabled: true
java_multiple_files: true
java_package_prefix: com.zcong1993.example
go_package_prefix:
default: github.com/zcong1993/grpc-example/pb
except:
- buf.build/googleapis/googleapis

这个 go_package_prefix 可以指定 go 语言 package 前缀,后续的会根据 pb 源文件的相对路径拼接. 更多配置参数可以查看文档 https://docs.buf.build/generate/managed-mode. 不再需要在 pb 文件中指定这些参数.

最后,buf 使用 yaml 来配置代码生成插件:

# buf.gen.yaml
version: v1
plugins:
- name: go
out: go
opt: paths=source_relative
- name: go-grpc
out: go
opt:
- paths=source_relative
- require_unimplemented_servers=false

这样就相当于上面的配置,是不是感觉门槛和使用方面体验好了很多.

lint 工具

静态检查有助于提高代码质量,和提前发现一些错误.

例如可以统一风格:

// wrong
message Test_Message {
string fileUrl = 1;
}

// right
message TestMessage {
string file_url = 1;
}

详细的文档可以查看文档 https://docs.buf.build/lint/rules,相当于一份最佳实践. 对于团队而言,统一代码风格也是非常重要的.

breaking change 检查

Protobuf 比 JSON 更容易产生不兼容性,并且很多时候会在不经意间产生不兼容性. 所以需要检测工具来进行检查和约束,让开发人员意识到自己做的操作会造成什么后果. buf 提供一个非兼容性检查工具,可以和版本管理中的某个版本进行比对.

例如: 最常见的不兼容就是修改字段类型或者修改字段名称.

message LoginRequest {
- string email = 1;
+ int64 email = 1;
string password = 2;
}

使用命令检测可以看到如下错误:

buf breaking --against ".git#branch=master"
# proto/petstoreapis/petstore/v1/petstore.proto:77:3:Field "1" on message "LoginRequest" changed type from "string" to "int64".

怎么使用?

初始化项目

buf mod init

然后它会生成一个 buf.yaml 文件,里面是项目的元信息,例如依赖的插件等。

可以在里面写一些依赖,例如:

version: v1
deps:
- buf.build/googleapis/googleapis # 常用的 Google API
- buf.build/grpc-ecosystem/grpc-gateway
- buf.build/envoyproxy/protoc-gen-validate
lint:
use:
- DEFAULT
build:
roots:
- api
- third_party
breaking:
use:
- FILE

上面的 build.roots 它可以告诉 buf 在那里寻找 proto 文件。我们指定 api 文件夹和 third_party 文件夹,这样 buf 就会在这两个文件夹里面寻找 proto 文件。

然后执行 buf mod update 拉取依赖,并且会生成 buf.lock 文件锁定版本,可以到 https://buf.build/ 里面去搜索对应的包

用到的几个文件介绍

  • buf.yaml:它是主配置文件,负责模块的名称、依赖项以及 lint 的规则。
  • buf.lock:就是一个依赖的版本锁
  • buf.gen.yaml:定义一些插件的配置,例如生成 go 代码的配置
  • buf.work.yaml:配置一个工作区,可以指定多个模块,然后可以一次性 lint、breaking change 检查等

generate code 生成代码

生成代码也是我们用这个工具的核心功能。配置生成代码的模板规则文件 buf.gen.yaml

version: v1
plugins:
- plugin: cpp
out: gen/proto/cpp
- plugin: java
out: gen/proto/java
- plugin: go
out: gen/proto/go
opt: paths=source_relative
- plugin: go-grpc
out: gen/proto/go
opt: paths=source_relative

这里配置了4个插件 分别的cpp java go go-grpc 用于生成c++ java go grpc代码

c++ 生成的源码放到gen/proto/cpp中

java 生成的源码放到 gen/proto/java中

go生成的源码放到 gen/proto/go中

go-grpc生成的源码放到 gen/proto/go中

执行生成代码命令 --template 参数是指定模板的路径 不指定默认会在当前执行目录查找 buf.gen.yaml

buf generate perapis --template buf.gen.yaml

References